package org.xutils.image;

import ohos.agp.components.Image;
import ohos.agp.components.element.Element;
import ohos.agp.components.element.PixelMapElement;
import ohos.app.Context;
import ohos.media.image.PixelMap;
import ohos.media.image.common.PixelFormat;
import ohos.media.image.common.Size;
import org.xutils.cache.LruCache;
import org.xutils.cache.LruDiskCache;
import org.xutils.common.Callback;
import org.xutils.common.task.Priority;
import org.xutils.common.task.PriorityExecutor;
import org.xutils.common.util.IOUtil;
import org.xutils.common.util.LogUtil;
import org.xutils.common.util.TextUtils;
import org.xutils.ex.FileLockedException;
import org.xutils.http.RequestParams;
import org.xutils.x;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Created by wyouflf on 15/10/9.
 * 图片加载控制
 */
/*package*/ final class ImageLoader implements
        Callback.PrepareCallback<File, PixelMap>,
        Callback.CacheCallback<PixelMap>,
        Callback.ProgressCallback<PixelMap>,
        Callback.TypedCallback<PixelMap>,
        Callback.Cancelable {

    private MemCacheKey key;
    private ImageOptions options;
    private WeakReference<Image> viewRef;
    private int fileLockedExceptionRetryCount = 0;

    private final static AtomicLong SEQ_SEEK = new AtomicLong(0);
    private final long seq = SEQ_SEEK.incrementAndGet();

    private volatile boolean stopped = false;
    private volatile boolean cancelled = false;
    private volatile boolean skipOnWaitingCallback = false;
    private volatile boolean skipOnFinishedCallback = false;
    private Callback.Cancelable httpCancelable;
    private Callback.CommonCallback<PixelMap> callback;
    private Callback.PrepareCallback<File, PixelMap> prepareCallback;
    private Callback.CacheCallback<PixelMap> cacheCallback;
    private Callback.ProgressCallback<PixelMap> progressCallback;

    private final static String DISK_CACHE_DIR_NAME = "xUtils_img";
    private final static Executor EXECUTOR = new PriorityExecutor(10, false);
    private final static int MEM_CACHE_MIN_SIZE = 1024 * 1024 * 4; // 4M
    private final static LruCache<MemCacheKey, PixelMap> MEM_CACHE =
            new LruCache<MemCacheKey, PixelMap>(MEM_CACHE_MIN_SIZE) {
                private boolean deepClear = false;

                @Override
                protected int sizeOf(MemCacheKey key, PixelMap value) {
                    if (value instanceof PixelMap) {
                        return value.getBaseDensity();
                    }
//                    else if (value instanceof GifDrawable) {
//                        return ((GifDrawable) value).getByteCount();
//                    }
                    return super.sizeOf(key, value);
                }

                @Override
                public void trimToSize(int maxSize) {
                    if (maxSize < 0) {
                        deepClear = true;
                    }
                    super.trimToSize(maxSize);
                    deepClear = false;
                }

                @Override
                protected void entryRemoved(boolean evicted, MemCacheKey key, PixelMap oldValue, PixelMap newValue) {
                    super.entryRemoved(evicted, key, oldValue, newValue);
                    if (evicted && deepClear && oldValue instanceof ReusableDrawable) {
                        ((ReusableDrawable) oldValue).setMemCacheKey(null);
                    }
                }
            };

    static {
        int memClass = 1024 * 1024 * 1024;

        // Use 1/8th of the available memory for this memory cache.
        int cacheSize = 1024 * 1024 * memClass / 8;
        if (cacheSize < MEM_CACHE_MIN_SIZE) {
            cacheSize = MEM_CACHE_MIN_SIZE;
        }
        MEM_CACHE.resize(cacheSize);
    }

    private ImageLoader() {
    }

    /*package*/
    static void clearMemCache() {
        MEM_CACHE.evictAll();
    }

    /*package*/
    static void clearCacheFiles() {
        LruDiskCache.getDiskCache(DISK_CACHE_DIR_NAME).clearCacheFiles();
    }

    private final static HashMap<String, FakeImageView> FAKE_IMG_MAP = new HashMap<String, FakeImageView>();

    /**
     * load from Network or DiskCache, invoke in any thread.
     */
    /*package*/
    static Cancelable doLoadDrawable(final String url,
                                     final ImageOptions options,
                                     final Callback.CommonCallback<PixelMap> callback) {
        if (TextUtils.isEmpty(url)) {
            postArgsException(null, options, "url is null", callback);
            return null;
        }

        FakeImageView fakeImageView = new FakeImageView();
        return doBind(fakeImageView, url, options, 0, callback);
    }

    /**
     * load from Network or DiskCache, invoke in any thread.
     */
    /*package*/
    static Cancelable doLoadFile(final String url,
                                 final ImageOptions options,
                                 final Callback.CacheCallback<File> callback) {
        if (TextUtils.isEmpty(url)) {
            postArgsException(null, options, "url is null", callback);
            return null;
        }


        RequestParams params = createRequestParams(null, url, options);
        return x.http().get(params, callback);
    }

    /**
     * load from Network or DiskCache, invoke in ui thread.
     */
    /*package*/
    static Cancelable doBind(final Image view,
                             final String url,
                             final ImageOptions options,
                             final int fileLockedExceptionRetryCount,
                             final Callback.CommonCallback<PixelMap> callback) {
        // check params
        ImageOptions localOptions = options;
        {
            if (view == null) {
                postArgsException(null, localOptions, "view is null", callback);
                return null;
            }

            if (TextUtils.isEmpty(url)) {
                postArgsException(view, localOptions, "url is null", callback);
                return null;
            }

            if (localOptions == null) {
                localOptions = ImageOptions.DEFAULT;
            }
            localOptions.optimizeMaxSize(view);
        }

        // stop the old loader
        MemCacheKey key = new MemCacheKey(url, localOptions);
//        PixelMap oldDrawable = view.getPixelMap();
//        if (oldDrawable instanceof AsyncDrawable) {
//            ImageLoader loader = ((AsyncDrawable) oldDrawable).getImageLoader();
//            if (loader != null && !loader.stopped) {
//                if (key.equals(loader.key)) {
//                    // repetitive url and options binding to the same View.
//                    // not need callback to ui.
//                    return null;
//                } else {
//                    loader.cancel();
//                }
//            }
//        } else if (oldDrawable instanceof ReusableDrawable) {
//            MemCacheKey oldKey = ((ReusableDrawable) oldDrawable).getMemCacheKey();
//            if (oldKey != null && oldKey.equals(key)) {
//                MEM_CACHE.put(key, oldDrawable);
//            }
//        }

        // load from Memory Cache
        PixelMap memDrawable = null;
        if (localOptions.isUseMemCache()) {
            memDrawable = MEM_CACHE.get(key);
            if (memDrawable instanceof PixelMap) {
                PixelMap bitmap = memDrawable;
                if (bitmap == null || bitmap.isReleased()) {
                    memDrawable = null;
                }
            }
        }
        if (memDrawable != null) { // has mem cache
            boolean trustMemCache = false;
            try {
                if (callback instanceof ProgressCallback) {
                    try {
                        ((ProgressCallback) callback).onWaiting();
                    } catch (Throwable ex) {
                        LogUtil.e(ex.getMessage(), ex);
                    }
                }

                if (callback instanceof CacheCallback) {
                    try {
                        // 是否信任内存缓存. onStart 之后再次调用 onCache 时, 入参是磁盘缓存.
                        trustMemCache = ((CacheCallback<PixelMap>) callback).onCache(memDrawable);
                    } catch (Throwable ex) {
                        LogUtil.e(ex.getMessage(), ex);
                    }
                } else {
                    trustMemCache = true;
                }

                // hit mem cache
                if (trustMemCache) {
                    view.setScaleMode(localOptions.getImageScaleType());
                    view.setPixelMap(memDrawable);
                    if (callback != null) {
                        try {
                            callback.onSuccess(memDrawable);
                        } catch (Throwable ex) {
                            callback.onError(ex, true);
                        }
                    }
                    // goto finally
                } else {
                    // not trust the cache
                    // load from Network or DiskCache
                    ImageLoader loader = new ImageLoader();
                    loader.fileLockedExceptionRetryCount = fileLockedExceptionRetryCount;
                    loader.skipOnWaitingCallback = true;
                    return loader.doLoadRequest(view, url, localOptions, callback);
                }
            } catch (Throwable ex) {
                LogUtil.e(ex.getMessage(), ex);
                // try load from Network or DiskCache
                trustMemCache = false;
                ImageLoader loader = new ImageLoader();
                loader.fileLockedExceptionRetryCount = fileLockedExceptionRetryCount;
                loader.skipOnWaitingCallback = true;
                return loader.doLoadRequest(view, url, localOptions, callback);
            } finally {
                if (trustMemCache && callback != null) {
                    try {
                        callback.onFinished();
                    } catch (Throwable ex) {
                        LogUtil.e(ex.getMessage(), ex);
                    }
                }
            }
        } else {  /* memDrawable == null */
            // load from Network or DiskCache
            ImageLoader loader = new ImageLoader();
            loader.fileLockedExceptionRetryCount = fileLockedExceptionRetryCount;
            return loader.doLoadRequest(view, url, localOptions, callback);
        }
        return null;
    }

    private Cancelable doLoadRequest(Image view,
                                     String url,
                                     ImageOptions options,
                                     Callback.CommonCallback<PixelMap> callback) {

        this.viewRef = new WeakReference<Image>(view);
        this.options = options;
        this.key = new MemCacheKey(url, options);
        this.callback = callback;
        if (callback instanceof Callback.ProgressCallback) {
            this.progressCallback = (Callback.ProgressCallback<PixelMap>) callback;
        }
        if (callback instanceof Callback.PrepareCallback) {
            this.prepareCallback = (Callback.PrepareCallback<File, PixelMap>) callback;
        }
        if (callback instanceof Callback.CacheCallback) {
            this.cacheCallback = (Callback.CacheCallback<PixelMap>) callback;
        }

        // set loadingDrawable
//        PixelMap loadingDrawable =view.getPixelMap();
//        if (loadingDrawable == null || options.isForceLoadingDrawable()) {
//            loadingDrawable =  options.getLoadingDrawable(view);
//            view.setScaleMode(options.getPlaceholderScaleType());
//        }
        // 从像素颜色数组创建
//        int[] defaultColors = new int[] {5, 5, 5, 5, 6, 6, 3, 3, 3, 0};
//        PixelMap.InitializationOptions initializationOptions = new PixelMap.InitializationOptions();
//        initializationOptions.size = new Size(3, 2);
//        initializationOptions.pixelFormat = PixelFormat.ARGB_8888;
//        view.setPixelMap(AsyncDrawable.create(defaultColors,initializationOptions));

//        view.setPixelMap(options.getLoadingDrawable(view));

        // request
        RequestParams params = createRequestParams(view.getContext(), url, options);
        if (view instanceof FakeImageView) {
            synchronized (FAKE_IMG_MAP) {
                FAKE_IMG_MAP.put(view.hashCode() + url, (FakeImageView) view);
            }
        }
        return httpCancelable = x.http().get(params, this);
    }

    @Override
    public void cancel() {
        stopped = true;
        cancelled = true;
        if (httpCancelable != null) {
            httpCancelable.cancel();
        }
    }

    @Override
    public boolean isCancelled() {
        return cancelled || !validView4Callback(false);
    }

    @Override
    public void onWaiting() {
        if (!skipOnWaitingCallback && progressCallback != null) {
            progressCallback.onWaiting();
        }
    }

    @Override
    public void onStarted() {
        if (validView4Callback(true) && progressCallback != null) {
            progressCallback.onStarted();
        }
    }

    @Override
    public void onLoading(long total, long current, boolean isDownloading) {
        if (validView4Callback(true) && progressCallback != null) {
            progressCallback.onLoading(total, current, isDownloading);
        }
    }

    private static final Type loadType = File.class;

    @Override
    public Type getLoadType() {
        return loadType;
    }

    @Override
    public PixelMap prepare(File rawData) throws Throwable {
        if (!validView4Callback(true)) return null;

        if (!rawData.exists()) {
            throw new FileNotFoundException(rawData.getCanonicalPath());
        }

        try {
            PixelMap result = null;
            if (prepareCallback != null) {
                result = prepareCallback.prepare(rawData);
            }
            if (result == null) {
                result = ImageDecoder.decodeFileWithLock(rawData, options, this);
            }
            if (result != null) {
                if (result instanceof ReusableDrawable) {
                    ((ReusableDrawable) result).setMemCacheKey(key);
                    MEM_CACHE.put(key, result);
                }
            }
            return result;
        } catch (IOException ex) {
            IOUtil.deleteFileOrDir(rawData);
            throw ex;
        }
    }

    private boolean hasCache = false;

    @Override
    public boolean onCache(PixelMap result) {
        if (!validView4Callback(true)) return false;

        if (result != null) {
            hasCache = true;
            setSuccessDrawable4Callback(result);
            if (cacheCallback != null) {
                return cacheCallback.onCache(result);
            } else if (callback != null) {
                callback.onSuccess(result);
                return true;
            }
            return true;
        }

        return false;
    }

    @Override
    public void onSuccess(PixelMap result) {
        if (!validView4Callback(!hasCache)) return;

        if (result != null) {
            setSuccessDrawable4Callback(result);
            if (callback != null) {
                callback.onSuccess(result);
                LogUtil.e("图片请求成功success==" + result);
//                mRealBindImage.setPixelMap(result);
                viewRef.get().getContext().getUITaskDispatcher().asyncDispatch(new Runnable() {
                    @Override
                    public void run() {
                        viewRef.get().setPixelMap(result);
                    }
                });
            }
        }
    }

    @Override
    public void onError(Throwable ex, boolean isOnCallback) {
        stopped = true;
        if (!validView4Callback(false)) return;

        fileLockedExceptionRetryCount++;
        if (ex instanceof FileLockedException && fileLockedExceptionRetryCount < 1000/*max*/) {
            LogUtil.d("ImageFileLocked: " + key.url);
            x.task().postDelayed(new Runnable() {
                @Override
                public void run() {
                    Image imageView = viewRef.get();
                    if (imageView != null) {
                        doBind(imageView, key.url, options, fileLockedExceptionRetryCount, callback);
                    } else {
                        ImageLoader.this.onFinished();
                    }
                }
            }, 10);
            skipOnFinishedCallback = true;
        } else {
            LogUtil.e(key.url, ex);
            setErrorDrawable4Callback();
            if (callback != null) {
                callback.onError(ex, isOnCallback);
            }
        }
    }

    @Override
    public void onCancelled(CancelledException cex) {
        stopped = true;
        if (!validView4Callback(false)) return;

        if (callback != null) {
            callback.onCancelled(cex);
        }
    }

    @Override
    public void onFinished() {
        stopped = true;
        if (skipOnFinishedCallback) return;

        Image view = viewRef.get();
        if (view instanceof FakeImageView) {
            synchronized (FAKE_IMG_MAP) {
                FAKE_IMG_MAP.remove(view.hashCode() + key.url);
            }
        }

        if (callback != null) {
            callback.onFinished();
        }
    }

    private static RequestParams createRequestParams(Context context, String url, ImageOptions options) {
        RequestParams params = new RequestParams(url);
        if (context != null) {
            params.setContext(context);
        }
        params.setCacheDirName(DISK_CACHE_DIR_NAME);
        params.setConnectTimeout(1000 * 8);
        params.setPriority(Priority.BG_LOW);
        params.setExecutor(EXECUTOR);
        params.setCancelFast(true);
        params.setUseCookie(false);
        if (options != null) {
            ImageOptions.ParamsBuilder paramsBuilder = options.getParamsBuilder();
            if (paramsBuilder != null) {
                params = paramsBuilder.buildParams(params, options);
            }
        }
        return params;
    }

    private boolean validView4Callback(boolean forceValidAsyncDrawable) {
        final Image view = viewRef.get();
//        LogUtil.e("图片请求validView4Callback"+(view.getPixelMap() instanceof AsyncDrawable));
        if (view != null) {
            PixelMap otherDrawable = view.getPixelMap();
            if (otherDrawable instanceof PixelMap) {
//                ImageLoader otherLoader = ((AsyncDrawable) otherDrawable).getImageLoader();
//                if (otherLoader != null) {
//                    if (otherLoader == this) {
//                        return true;
//                    } else {
//                        if (this.seq > otherLoader.seq) {
//                            otherLoader.cancel();
//                            return true;
//                        } else {
//                            this.cancel();
//                            return false;
//                        }
//                    }
//                }
                return true;
            } else if (forceValidAsyncDrawable) {
                this.cancel();
                return false;
            }
            return true;
        }
        return false;
    }

    private void setSuccessDrawable4Callback(final PixelMap drawable) {
        final Image view = viewRef.get();
        view.getContext().getUITaskDispatcher().asyncDispatch(new Runnable() {
            @Override
            public void run() {
                if (view != null) {
                    view.setScaleMode(options.getImageScaleType());
//            if (drawable instanceof GifDrawable) {
//                if (view.getScaleMode() == Image.ScaleMode.CENTER) {
//                    view.setScaleMode(Image.ScaleMode.INSIDE);
//                }
////                view.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
//            }
                    if (options.getAnimation() != null) {
                        ImageAnimationHelper.animationDisplay(view, drawable, options.getAnimation());
                    } else if (options.isFadeIn()) {
                        ImageAnimationHelper.fadeInDisplay(view, drawable);
                    } else {
                        view.setPixelMap(drawable);
                    }
                }
            }});
        }

        private void setErrorDrawable4Callback () {
            final Image view = viewRef.get();
            view.getContext().getUITaskDispatcher().asyncDispatch(new Runnable() {
                @Override
                public void run() {
                    if (view != null) {
                        PixelMap drawable = (PixelMap) options.getFailureDrawable(view);
                        view.setScaleMode(options.getPlaceholderScaleType());
                        view.setPixelMap(drawable);
                    }
                }
            });

        }

        private static void postArgsException (
        final Image view, final ImageOptions options,
        final String exMsg, final Callback.CommonCallback<?> callback){
            x.task().autoPost(new Runnable() {
                @Override
                public void run() {
                    x.app().getContext().getUITaskDispatcher().asyncDispatch(new Runnable() {
                        @Override
                        public void run() {


                    try {
                        if (callback instanceof ProgressCallback) {
                            ((ProgressCallback) callback).onWaiting();
                        }
                        if (view != null && options != null) {
                            view.setScaleMode(options.getPlaceholderScaleType());
                            view.setPixelMap(options.getFailureDrawable(view));
                        }
                        if (callback != null) {
                            callback.onError(new IllegalArgumentException(exMsg), false);
                        }
                    } catch (Throwable ex) {
                        if (callback != null) {
                            try {
                                callback.onError(ex, true);
                            } catch (Throwable throwable) {
                                LogUtil.e(throwable.getMessage(), throwable);
                            }
                        }
                    } finally {
                        if (callback != null) {
                            try {
                                callback.onFinished();
                            } catch (Throwable throwable) {
                                LogUtil.e(throwable.getMessage(), throwable);
                            }
                        }
                    }
                        }
                    });
                }
            });
        }

        private final static class FakeImageView extends Image {
            private final int hashCode;
            private PixelMap drawable;
            private final static AtomicInteger hashCodeSeed = new AtomicInteger(0);

            public FakeImageView() {
                super(x.app());
                hashCode = hashCodeSeed.incrementAndGet();
            }

            @Override
            public int hashCode() {
                return hashCode;
            }

//        @Override
//        public void setPixelMap(PixelMap PixelMap) {
//            this.drawable = drawable;
//        }
//
//        @Override
//        public PixelMap getDrawable() {
//            return drawable;
//        }


            @Override
            public void setImageElement(Element element) {
//            super.setImageElement(element);
//            this.drawable=  element;
            }

            @Override
            public Element getImageElement() {
                return null;
            }
        }
    }
